# -*- coding:utf-8 -*-
"""
COMTRADE CFG文件解析器 - 精简优化版
Author: Optimized from original by Jackzhangqf
Date: 2025-08
"""

import os
from dataclasses import dataclass
from typing import List, Tuple, Optional, Union
import traceback


@dataclass
class ChannelInfo:
    """通用通道信息基类"""
    mno: int
    cch_id: str
    cph: str = ""
    cccbm: str = ""


@dataclass
class AnalogChannel(ChannelInfo):
    """模拟通道信息"""
    munit: str = ""
    ma: float = 1.0
    mb: float = 0.0
    cskew: float = 0.0
    mmin: int = -32768
    mmax: int = 32767
    mprimary: float = 1.0
    msecondary: float = 1.0
    mps: str = "P"


@dataclass
class DigitalChannel(ChannelInfo):
    """数字通道信息"""
    my: int = 0


class CfgParser:
    """COMTRADE CFG文件解析器 - 精简优化版"""

    # 默认编码尝试顺序
    ENCODINGS = ['gb2312', 'utf-8', 'latin-1', 'gbk']

    def __init__(self, cfg_path: str):
        self.path = cfg_path
        self.datafile_name = os.path.splitext(cfg_path)[0]

        # 基本信息
        self.station_name = "Default"
        self.dev_name = "Default_name"
        self.version = 1999

        # 通道信息
        self.total_channels = 0
        self.analog_count = 0
        self.digital_count = 0
        self.analog_channels: List[AnalogChannel] = []
        self.digital_channels: List[DigitalChannel] = []

        # 采样信息
        self.reference_frequency = 50.0
        self.sample_sections = 0
        self.sample_rates: List[int] = []
        self.sample_points: List[int] = []

        # 时间信息
        self.start_time: List[str] = []
        self.trigger_time: List[str] = []
        self.time_multiplier = 1

        # 数据格式
        self.data_format = "BINARY"

        # 计算字段
        self.frame_size = 0  # 单帧数据字节数
        self.file_size = 0  # 整个数据文件字节数
        self.digital_bytes = 0  # 数字量字节数

    @property
    def sample_rate_points(self) -> Tuple[List[int], List[int]]:
        """兼容原API的采样率点数属性"""
        return self.sample_rates, self.sample_points

    @property
    def start_t(self) -> List[str]:
        """兼容原API的开始时间"""
        return self.start_time

    @property
    def end_t(self) -> List[str]:
        """兼容原API的结束时间"""
        return self.trigger_time

    @property
    def timemult(self) -> int:
        """兼容原API的时间乘子"""
        return self.time_multiplier

    @property
    def a_c(self) -> int:
        """兼容原API的模拟通道数"""
        return self.analog_count

    @property
    def d_c(self) -> int:
        """兼容原API的数字通道数"""
        return self.digital_count

    @property
    def Achannel_info(self) -> List[AnalogChannel]:
        """兼容原API的模拟通道信息"""
        return self.analog_channels

    @property
    def Dchannel_info(self) -> List[DigitalChannel]:
        """兼容原API的数字通道信息"""
        return self.digital_channels

    @property
    def data_len(self) -> int:
        """兼容原API的数据长度"""
        return self.frame_size

    @property
    def datafile_len(self) -> int:
        """兼容原API的文件长度"""
        return self.file_size

    @property
    def bin_len(self) -> int:
        """兼容原API的二进制长度"""
        return self.digital_bytes

    def parse(self) -> bool:
        """解析CFG文件主入口"""
        try:
            if not self._validate_file():
                return False

            if not self._parse_content():
                return False

            self._calculate_sizes()
            return True

        except Exception as e:
            print(f"解析CFG文件失败: {e}")
            print(traceback.format_exc())
            raise e

    def _validate_file(self) -> bool:
        """验证文件有效性"""
        exts = ['.cfg', '.CFG']
        for ext in exts:
            file_path = self.path[:-4] + ext
            if os.path.exists(file_path):
                self.path = file_path
                return True

        if not self.path.upper().endswith('.CFG'):
            print("非CFG文件格式")
            return False

        return False

    def _parse_content(self) -> bool:
        """解析文件内容"""
        content = self._read_file_with_encoding()
        if content is None:
            return False

        lines = [line.strip() for line in content.split('\n') if line.strip()]
        return self._parse_lines(lines)

    def _read_file_with_encoding(self) -> Optional[str]:
        """尝试不同编码读取文件"""
        for encoding in self.ENCODINGS:
            try:
                with open(self.path, 'r', encoding=encoding) as f:
                    return f.read()
            except UnicodeDecodeError:
                continue

        print(f"无法使用已知编码读取文件: {self.path}")
        return None

    def _parse_lines(self, lines: List[str]) -> bool:
        """解析文件行内容"""
        try:
            line_idx = 0

            # 第1行: 站点信息
            if line_idx < len(lines):
                parts = self._split_line(lines[line_idx])
                if len(parts) >= 3:
                    self.station_name = parts[0]
                    self.dev_name = parts[1]
                    self.version = self._safe_int(parts[2], 1999)
                line_idx += 1

            # 第2行: 通道数量
            if line_idx < len(lines):
                parts = self._split_line(lines[line_idx])
                if len(parts) >= 3:
                    self.total_channels = self._safe_int(parts[0])
                    self.analog_count = self._extract_number(parts[1], 'A')
                    self.digital_count = self._extract_number(parts[2], 'D')
                line_idx += 1

            # 模拟通道
            for i in range(self.analog_count):
                if line_idx < len(lines):
                    channel = self._parse_analog_channel(lines[line_idx])
                    if channel:
                        self.analog_channels.append(channel)
                    line_idx += 1

            # 数字通道
            for i in range(self.digital_count):
                if line_idx < len(lines):
                    channel = self._parse_digital_channel(lines[line_idx])
                    if channel:
                        self.digital_channels.append(channel)
                    line_idx += 1

            # 剩余字段
            remaining_fields = [
                'reference_frequency',
                'sample_sections',  # 采样率信息（动态数量）
                'start_time',
                'trigger_time',
                'data_format',
                'time_multiplier'
            ]

            # 基准频率
            if line_idx < len(lines):
                parts = self._split_line(lines[line_idx])
                self.reference_frequency = self._safe_float(parts[0], 50.0)
                line_idx += 1

            # 采样段数
            if line_idx < len(lines):
                parts = self._split_line(lines[line_idx])
                self.sample_sections = self._safe_int(parts[0])
                line_idx += 1

            # 采样率和点数
            for i in range(self.sample_sections):
                if line_idx < len(lines):
                    parts = self._split_line(lines[line_idx])
                    if len(parts) >= 2:
                        rate = self._safe_int(parts[0])
                        points = self._safe_int(parts[1])
                        self.sample_rates.append(rate)
                        self.sample_points.append(points)
                    line_idx += 1

            # 时间信息
            if line_idx < len(lines):
                parts = self._split_line(lines[line_idx])
                self.start_time = parts[:2] if len(parts) >= 2 else ["", ""]
                line_idx += 1

            if line_idx < len(lines):
                parts = self._split_line(lines[line_idx])
                self.trigger_time = parts[:2] if len(parts) >= 2 else ["", ""]
                line_idx += 1

            # 数据格式
            if line_idx < len(lines):
                parts = self._split_line(lines[line_idx])
                self.data_format = parts[0] if parts else "BINARY"
                line_idx += 1

            # 时间乘子
            if line_idx < len(lines):
                parts = self._split_line(lines[line_idx])
                self.time_multiplier = self._safe_int(parts[0], 1)
                line_idx += 1

            return True

        except Exception as e:
            print(f"解析行内容失败: {e}")
            return False

    def _parse_analog_channel(self, line: str) -> Optional[AnalogChannel]:
        """解析模拟通道行"""
        parts = self._split_line(line)
        if len(parts) < 13:
            print(f"模拟通道格式错误: {line}")
            return None

        try:
            return AnalogChannel(
                mno=self._safe_int(parts[0]),
                cch_id=parts[1],
                cph=parts[2],
                cccbm=parts[3],
                munit=parts[4],
                ma=self._safe_float(parts[5], 1.0),
                mb=self._safe_float(parts[6], 0.0),
                cskew=self._safe_float(parts[7], 0.0),
                mmin=self._safe_int(self._safe_float(parts[8]), -32768),
                mmax=self._safe_int(self._safe_float(parts[9]), 32767),
                mprimary=self._safe_float(parts[10], 1.0),
                msecondary=self._safe_float(parts[11], 1.0),
                mps=parts[12][0] if parts[12] else 'P'
            )
        except Exception as e:
            print(f"解析模拟通道失败: {e}")
            return None

    def _parse_digital_channel(self, line: str) -> Optional[DigitalChannel]:
        """解析数字通道行"""
        parts = self._split_line(line)
        if len(parts) < 5:
            print(f"数字通道格式错误: {line}")
            return None

        try:
            return DigitalChannel(
                mno=self._safe_int(parts[0]),
                cch_id=parts[1],
                cph=parts[2],
                cccbm=parts[3],
                my=self._safe_int(parts[4][0] if parts[4] else "0")
            )
        except Exception as e:
            print(f"解析数字通道失败: {e}")
            return None

    def _calculate_sizes(self):
        """计算数据文件相关尺寸"""
        self.frame_size = self.analog_count * 2 + 8  # 模拟通道*2 + 8字节时间戳

        # 数字通道字节数计算
        temp1 = self.digital_count // 16
        d_temp = self.digital_count % 16

        if d_temp:
            # 有余数的情况：需要额外分配一个16位单元(2字节)
            self.digital_bytes = temp1 * 2 + 2
        else:
            # 无余数的情况：刚好按16位边界对齐
            self.digital_bytes = temp1 * 2

        self.frame_size += self.digital_bytes

        if self.sample_points:
            if len(self.sample_points) > 0:
                total_samples = self.sample_points[-1]
                self.file_size = self.frame_size * total_samples



    def compare_with(self, other: 'CfgParser', check_station: bool = True) -> bool:
        """
        比较两个CFG文件是否兼容

        Args:
            other: 另一个CFG解析器实例
            check_station: 是否检查站点和设备名称
        """
        if not isinstance(other, CfgParser):
            return False

        # 检查站点信息
        if check_station:
            if (self.station_name != other.station_name or
                    self.dev_name != other.dev_name):
                return False

        # 检查通道数量
        if (self.analog_count != other.analog_count or
                self.digital_count != other.digital_count):
            return False

        # 检查通道名称
        for i, ch in enumerate(self.analog_channels):
            if (i >= len(other.analog_channels) or
                    ch.cch_id != other.analog_channels[i].cch_id):
                return False

        for i, ch in enumerate(self.digital_channels):
            if (i >= len(other.digital_channels) or
                    ch.cch_id != other.digital_channels[i].cch_id):
                return False

        return True

    def get_channel_names(self) -> List[str]:
        """获取所有通道名称列表"""
        names = [ch.cch_id for ch in self.analog_channels]
        names.extend(ch.cch_id for ch in self.digital_channels)
        return names

    def find_channel_by_name(self, name: str) -> Optional[Union[AnalogChannel, DigitalChannel]]:
        """根据名称查找通道"""
        for ch in self.analog_channels:
            if ch.cch_id == name:
                return ch

        for ch in self.digital_channels:
            if ch.cch_id == name:
                return ch

        return None

    def print_summary(self):
        """打印配置文件摘要信息"""
        print(f"=== CFG文件信息摘要 ===")
        print(f"厂站名称: {self.station_name}")
        print(f"装置名称: {self.dev_name}")
        print(f"版本: {self.version}")
        print(f"模拟通道: {self.analog_count}个")
        print(f"数字通道: {self.digital_count}个")
        print(f"总通道数: {self.total_channels}个")
        print(f"采样频率: {self.reference_frequency}Hz")
        print(f"采样段数: {self.sample_sections}个")
        if self.sample_rates:
            print(f"采样率: {self.sample_rates}")
            print(f"采样点数: {self.sample_points}")
        print(f"数据格式: {self.data_format}")
        print(f"单帧大小: {self.frame_size}字节")
        print(f"文件大小: {self.file_size}字节")
        print(f"时间戳乘数: {self.time_multiplier}")

    # 工具方法
    @staticmethod
    def _split_line(line: str) -> List[str]:
        """分割并清理行数据"""
        return [item.strip() for item in line.split(',')]

    @staticmethod
    def _safe_int(value: Union[str, float, int], default: int = 0) -> int:
        """安全转换为整数"""
        try:
            if isinstance(value, str):
                return int(float(value))  # 处理 "100.000000" 格式
            return int(value)
        except (ValueError, TypeError):
            return default

    @staticmethod
    def _safe_float(value: Union[str, float, int], default: float = 0.0) -> float:
        """安全转换为浮点数"""
        try:
            return float(value)
        except (ValueError, TypeError):
            return default

    @staticmethod
    def _extract_number(text: str, suffix: str) -> int:
        """从带后缀的字符串中提取数字 (如 "10A" -> 10)"""
        try:
            if suffix in text:
                return int(text.split(suffix)[0])
            return int(text)
        except (ValueError, IndexError):
            return 0


# 兼容性包装类
class Cfg_file:
    """原API兼容包装器"""

    def __init__(self, path: str):
        self._parser = CfgParser(path)

        # 暴露所有原有属性
        for attr in ['station_name', 'version', 'dev_name', 'channel', 'reference_frequency',
                     'sample_section', 'data_format', 'path', 'datafile_name']:
            if hasattr(self._parser, attr):
                setattr(self, attr, getattr(self._parser, attr))

    def __getattr__(self, name):
        """代理到新解析器"""
        return getattr(self._parser, name)

    def parse_file(self) -> bool:
        """兼容原方法名"""
        return self._parser.parse()

    def parse(self) -> bool:
        """兼容原方法名"""
        return self._parser.parse()

    def compare_cfgs(self, other) -> bool:
        """兼容原方法名"""
        if isinstance(other, Cfg_file):
            return self._parser.compare_with(other._parser, check_station=True)
        elif isinstance(other, CfgParser):
            return self._parser.compare_with(other, check_station=True)
        return False

    def compare_chname(self, other) -> bool:
        """兼容原方法名"""
        if isinstance(other, Cfg_file):
            return self._parser.compare_with(other._parser, check_station=False)
        elif isinstance(other, CfgParser):
            return self._parser.compare_with(other, check_station=False)
        return False

    def cfg_info_echo(self):
        """兼容原方法名"""
        self._parser.print_summary()


# 兼容原有类名
Achannel_info = AnalogChannel
Dchannel_info = DigitalChannel


# 使用示例
def example_usage():
    """使用示例"""
    # 新API使用方式 (推荐)
    parser = CfgParser("example.cfg")
    if parser.parse():
        parser.print_summary()

        # 查找特定通道
        voltage_ch = parser.find_channel_by_name("电压")
        if voltage_ch:
            print(f"找到电压通道: {voltage_ch}")

        # 比较两个CFG文件
        parser2 = CfgParser("example2.cfg")
        if parser2.parse():
            if parser.compare_with(parser2):
                print("两个CFG文件兼容")

    # 原API兼容使用方式
    cfg = Cfg_file("example.cfg")
    if cfg.parse():
        cfg.cfg_info_echo()


if __name__ == "__main__":
    example_usage()